/*******************************************************************************
        
        copyright:		Copyright (C) dcrypt contributors 2008. All rights reserved
        				
        license:		BSD style: $(LICENSE)
        		
        author(s): 		Thomas Dixon (Modified by Mike Reinhold)
        				
*******************************************************************************/
module tango.util.cipher.modes.CBC;

import tango.util.cipher.Cipher;

debug(UnitTest){
	import tango.util.cipher.modes.CBC;
	import tango.util.cipher.XTEA;
}

/** This class implements the cipher block chaining (CBC) block mode. */
class CBC: BlockCipherMode {
	private {
		ubyte[] iv, previousBlock, currentBlock;
	}

	/** Null constructor */
	this() {
	}
	
	this(CBC other){
		(other._initialized) ? this(other.wrappedCipher.dup) : this();
		this.iv = other.iv.dup;
		this.previousBlock = other.previousBlock.dup;
		this.currentBlock = other.currentBlock.dup;
	}
	
	CBC dup() {
		return new CBC(this);
	}

	/***************************************************************************
	
			constructor
			
			Params:
			cipher = block cipher to wrap
	
	***************************************************************************/
	this(BlockCipher cipher) {
		wrappedCipher = cipher;
	}

	/***************************************************************************
	
			cipher
			
			Returns:
			the underlying cipher we are wrapping.

	***************************************************************************/
	BlockCipher cipher() {
		return wrappedCipher;
	}
	
	/***************************************************************************
	
			cipher
			
			Params:
			cipher_ = block cipher to wrap
			
			Remarks:
			Mutator method which sets the wrapped block cipher.

	***************************************************************************/
	void cipher(BlockCipher cipher_) {
		wrappedCipher = cipher_;
	}

	/***************************************************************************
	
			name
			
			Returns:
			the name of the wrapped cipher and its mode.
			
			Remarks:
			If this were wrapping Serpent the result would be "Serpent/CBC"

	***************************************************************************/
	string name() {
		return ((_initialized) ? wrappedCipher.name : "") ~ "CBC";
	}
	
	/***************************************************************************
	
			requiresPadding
			
			Returns:
			does this mode require padding?

	***************************************************************************/
	bool requiresPadding(){
		return true;
	}
	
	/***************************************************************************
	
			init
			
			Params:
			encrypt = the direction of the cipher
			key =	  symmetric key to be used
			
			Remarks:
			A new IV is generated by this method

	***************************************************************************/
	void init(bool encrypt, SymmetricKey key) {
		init(encrypt, key, new IVParameters());
	}

	/***************************************************************************
	
			init
			
			Params:
			encrypt =  the direction of the cipher
			key =	   symmetric key to be used
			ivParams = the initialization vector to be used
			
			Remarks:
			If the parameters are incorrect an exception will be thrown.

	***************************************************************************/
	void init(bool encrypt, SymmetricKey key, IVParameters ivParams) {
		if(!ivParams)
			invalid(name() ~ ": Block mode requires IV (use ParametersWithIV)");
		if(ivParams.iv.length < blockSize)
			invalid(name() ~ ": IV must be at least as long as cipher block size");

		_encrypt = encrypt;

		//initialize the wrapped cipher
		wrappedCipher.init(_encrypt, key);

		//setup the state
		iv = ivParams.iv[0 .. blockSize];
		currentBlock = new ubyte[blockSize];
		previousBlock = new ubyte[blockSize];
		previousBlock[] = iv;

		_initialized = true;
	}

	/***************************************************************************
	
			update
			
			Params:
			input_ = the block to be processed
			output_ = the output buffer
						
			Returns:
			the number of bytes processed
			
			Remarks:
			Update the input block based on the current cipher direction and
 			place it in the output block.

	***************************************************************************/
	uint update(void[] input_, void[] output_) {
		if(!_initialized)
			invalid(name() ~ ": Block mode not initialized");

		
		ubyte[] input = cast(ubyte[]) input_, 
				output = cast(ubyte[]) output_;

		if(input.length < blockSize)
			invalid(name() ~ ": Input buffer too short");

		if(output.length < blockSize)
			invalid(name() ~ ": Output buffer too short");

		if(_encrypt) {
			//XOR the input with the previous block
			previousBlock[0..blockSize] ^= input[0..blockSize];

			//encrypt the block
			wrappedCipher.update(previousBlock, previousBlock);

			//store the ciphertext
			output[0 .. blockSize] = previousBlock[0 .. blockSize];
		} else {
			ubyte[] temp = input[0 .. blockSize];

			//decrypt the block
			wrappedCipher.update(temp, currentBlock);
			
			//get the plaintext
			output[0..blockSize] = (currentBlock[0..blockSize] ^ previousBlock[0..blockSize]);

			// Store the previous block
			previousBlock[0 .. blockSize] = temp[0 .. blockSize];
		}

		//return the number of processed bytes
		return blockSize;
	}

	/***************************************************************************
	
			blockSize
											
			Returns:
			the size of a block as specified by the wrapped cipher

	***************************************************************************/
	uint blockSize() {
		return wrappedCipher.blockSize;
	}

	/***************************************************************************
	
			ivSize
											
			Returns:
			the length of the initialization vector in bytes

	***************************************************************************/
	uint ivSize() {
		return wrappedCipher.ivSize;
	}

	
	/***************************************************************************
	
			validKeySizes
											
			Returns:
			an array containing the byte length of every possible valid key

	***************************************************************************/
	uint[] validKeySizes() {
		return wrappedCipher.validKeySizes;
	}

	/***************************************************************************
			
			reset
											
			Returns:
			resets the BlockCipherMode and the wrapped cipher

	***************************************************************************/
	void reset() {
		previousBlock[] = iv;
		wrappedCipher.reset();
	}

	/** Test vectors for CBC mode. Assumes XTEA passes test vectors. */
	debug(UnitTest) {
		unittest {			
			static const string[] test_keys = [
					"00000000000000000000000000000000",
					"00000000000000000000000000000000",
					"0123456789abcdef0123456789abcdef"];

			static const string[] test_ivs = [
   					"00000000000000000000000000000000",
   					"00000000000000000000000000000000",
   					"00000000000000000000000000000000"];
			
			static const string[]
					test_plaintexts = [
							"00000000000000000000000000000000" ~ "00000000000000000000000000000000",
							"41414141414141414141414141414141" ~ "41414141414141414141414141414141",
							"01010101010101010101010101010101" ~ "01010101010101010101010101010101"];

			static const string[]
					test_ciphertexts = [
							"dee9d4d8f7131ed9b0e40a036a85d2c4" ~ "4602d6e67f0c603738197998166ef281",
							"ed23375a821a8c2d0e1f03d719874eaa" ~ "4b71be74f261e22f4cd2285883a61a23",
							"c09d3c606614d84b8d184fa29c5cb5f6" ~ "f26fa5a0b6b63ba0f7ebf2f8735f85e3"];

			CBC c = new CBC(new XTEA);
			ubyte[] buffer = new ubyte[32];
			string result;
			for(int i = 0; i < test_keys.length; i++) {
				SymmetricKey key = new SymmetricKey(ByteConverter.hexDecode(
						test_keys[i]));
				IVParameters params = new IVParameters(ByteConverter.hexDecode(test_ivs[i]));

				// Encryption 
				c.init(true, key, params);
				for(int j = 0; j < 32; j += c.blockSize)
					c.update(
							ByteConverter.hexDecode(test_plaintexts[i])[j .. j + c.blockSize],
							buffer[j .. j + c.blockSize]);
				result = ByteConverter.hexEncode(buffer);
				assert(result == test_ciphertexts[i], c.name() ~ ": (" ~ result ~ ") != (" ~ test_ciphertexts[i] ~ ")");

				// Decryption
				c.init(false, key, params);
				for(int j = 0; j < 32; j += c.blockSize)
					c.update(
							ByteConverter.hexDecode(test_ciphertexts[i])[j .. j + c.blockSize],
							buffer[j .. j + c.blockSize]);
				result = ByteConverter.hexEncode(buffer);
				assert(result == test_plaintexts[i], c.name() ~ ": (" ~ result ~ ") != (" ~ test_plaintexts[i] ~ ")");
			}
		}
	}
}
